#!/usr/bin/env python
# -*- coding: utf-8 -*-
#===============================================================================
# Copyright 2011 zod.yslin
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 
# Author: zod.yslin
# Email: 
# File Name: argument.py
# Description: 
#   Default Arguments and Argument Names
#   Multimethods with its module mm (http://www.artima.com/weblogs/viewpost.jsp?thread=101605)
#   Arbitrary Argument Lists (foo(*arg))
#   Keyword Arguments (foo(**dict))
# Edit History: 
#   2011-09-14    File created.
#===============================================================================
# Simplest Arguments
def multiprint1( n, txt ):
    i = 0
    while i < n:
        print(txt)
        i = i + 1

# This throws an error
#multiprint1()

# Default Values
def multiprint2( n=5, txt="" ):
    i = 0
    while i < n:
        print(txt)
        i = i + 1
        
# This works just fine
multiprint2()

# Labels
def multiprint3( n=5, txt="" ):
    i = 0
    while i < n:
        print(txt)
        i = i + 1

# I want to call multiprint, but I'm happy with
# n = 5, so I don't want to reassign it.
# I can use the labels to set "txt" without having to set n.
multiprint3( txt="Hello World!" )

def ask_ok(prompt, retries=4, complaint='Yes or no!'):
    while True:
        ok = raw_input(prompt)
        if ok in ('y', 'ye', 'yes'): return True
        if ok in ('n', 'no', 'nop', 'nope'): return False
        retries = retries - 1
        if retries < 0: raise IOError('refusenik user')
        print(complaint)

#ask_ok("input(yes/no):")

i = 5

def f(arg=i):
    arg = arg + 3
    """
    The default values are evaluated at the point of function definition in the defining scope.
    """
    print("f(arg):", arg)

i = 6
f()  # print 8
f()  # print 8


def f(a, L=[]):
    """
    The default value is evaluated only once. 
    This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. 
    """
    L.append(a)
    return L

print(f(1))  # [1]
print(f(2))  # [1, 2]
print(f(3))  # [1, 2, 3]

def f(a, L=None):
    """
    Don't want the default to be shared between subsequent calls,
    Set it default value when it is None.
    """
    if L is None:
        L = []
    L.append(a)
    return L

print(f(1))  # [1]
print(f(2))  # [2]
print(f(3))  # [3]

def fail(a, b=1, c=2):
    """
    c must assign default value like b, or error occurs.
    """
    print("SyntaxError: non-default argument follows default argument")

def foo(a, b):
    """
    multimethods
    I'll give you my own definition, as I've come to understand them: 
        a function that has multiple versions, distinguished by the type of the arguments. 
    But this pattern gets tedious. (It also isn't very OO, but then, neither are multimethods, despite the name, IMO.) 
    """
    if isinstance(a, int) and isinstance(b, int):
        return a + b
    elif isinstance(a, float) and isinstance(b, float):
        return a - b
    elif isinstance(a, str) and isinstance(b, str):
        return a + "-" + b 
    else:
        raise TypeError("unsupported argument types (%s, %s)" % (type(a), type(b)))
    
print(foo(1, 2))
print(foo(3.1, 2.5))
print(foo("Go", "Modom"))


from mm import multimethod, multimethod2

@multimethod(int, int)
def multifoo(a, b):
    return a + b

@multimethod(float, float)
def multifoo(a, b):
    return a - b

@multimethod(str, str)
def multifoo(a, b):
    return a + "-" + b 

print(multifoo(1,2))
print(multifoo(3.3,2.1))
print(multifoo("ok","mm"))

#Default parameter values are somewhat against the nature of multimethods
#@multimethod(int, int)
#def foo(a, b=10):
#    print(a, b)
#
#foo(1)  # TypeError: no match

@multimethod(int, int)
def foo(a, b):
    print(a, b)

@multimethod(int)
def foo(a):
    foo(a, 10)  # This calls the previous foo()!
foo(1)
foo(1,2)

@multimethod2(int, int)
@multimethod2(int)
def foo2(a, b=10):
    print(a, b)

foo2(1)
foo2(1,2)

#==============================================================================
#   Arbitrary Argument Lists
#==============================================================================
def perform( fun, *args ):
    fun( *args )

def action1():
    print("action1")
    pass

def action2( arg1 ):
    print("action2", arg1)
    pass

def action3( arg1, arg2 ):
    print("action3", arg1, arg2)
    pass

perform( action1 )
perform( action2, 1 )
perform( action3, 1, 2 )


def Perform(f):
    f()
# lambda version
Perform(lambda: action1())
Perform(lambda: action2(1))
Perform(lambda: action3(1, 2))

#==============================================================================
#   Keyword Arguments (foo(**dict))
#==============================================================================
def cheeseshop(kind, *arguments, **keywords):
    """
    When a final formal parameter of the form **name is present, 
    it receives a dictionary (see Mapping Types — dict) containing all keyword arguments except for those corresponding to a formal parameter.
    This may be combined with a formal parameter of the form *name (described in the next subsection) 
    which receives a tuple containing the positional arguments beyond the formal parameter list. (*name must occur before **name.) 
    """
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    keys = sorted(keywords.keys())
    for kw in keys:
        print(kw, ":", keywords[kw])

cheeseshop("Limburger", "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           shopkeeper="Michael Palin",
           client="John Cleese",
           sketch="Cheese Shop Sketch")
